﻿namespace ASPNETCachingModule
{
    using System;
    using System.IO;
    using System.Web;
    using CacheProvider;

    /// <summary>
    /// An HTTP Module that provides caching the HTTP response
    /// in a provider based manner.
    /// </summary>
    public class CacheProviderModule : IHttpModule
    {

        #region IHttpModule Members

        public void Dispose()
        {

        }

        public void Init(HttpApplication context)
        {
            context.ResolveRequestCache += ContextResolveRequestCache;
            context.UpdateRequestCache += ContextUpdateRequestCache;
        }

        static void ContextUpdateRequestCache(object sender, EventArgs e)
        {
            var httpApplication = sender as HttpApplication;
            
            if (httpApplication == null) return;

            var context = httpApplication.Context;

            if (context.Response.StatusCode != 200 || context.Trace.IsEnabled) return;

            var responseCacheFilter = context.Response.Filter as ResponseCacheFilter;

            if (responseCacheFilter != null)
            {
                CacheBroker.Put(context.Request.FilePath, responseCacheFilter.GetResponseData());
            }
        }

        static void ContextResolveRequestCache(object sender, EventArgs e)
        {
            var httpApplication = sender as HttpApplication;
            
            if (httpApplication != null)
            {
                var context = httpApplication.Context;
                var cachedResponse = CacheBroker.Get(context.Request.FilePath) as byte[];
                if (cachedResponse == null)
                {
                    var responseCacheFilter = new ResponseCacheFilter(httpApplication.Response.Filter);
                    httpApplication.Response.Filter = responseCacheFilter;
                    return;
                }

                context.Response.ClearContent();
                context.Response.BinaryWrite(cachedResponse);

                httpApplication.CompleteRequest();
            }
        }

        #endregion
    }

    [Serializable]
    public class ResponseCacheFilter : Stream
    {
        private readonly Stream _originalStream;
        private readonly MemoryStream _responseCopy;

        public ResponseCacheFilter(Stream originalStream)
        {
            this._originalStream = originalStream;
            _responseCopy = new MemoryStream();
        }

        public byte[] GetResponseData()
        {
            return this._responseCopy.ToArray();
        }

        public override bool CanRead
        {
            get { return this._originalStream.CanRead; }
        }

        public override bool CanSeek
        {
            get { return this._originalStream.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return this._originalStream.CanWrite; }
        }

        public override void Flush()
        {
            this._originalStream.Flush();
            this._responseCopy.Flush();
        }

        public override long Length
        {
            get { return this._originalStream.Length; }
        }

        public override long Position
        {
            get
            {
                return this._originalStream.Position;
            }
            set
            {
                this._originalStream.Position = value;
            }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return this._originalStream.Read(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return this._originalStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            this._originalStream.SetLength(value);
            this._responseCopy.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            this._originalStream.Write(buffer, offset, count);
            this._responseCopy.Write(buffer, offset, count);
        }
    }
}